<?php
// +----------------------------------------------------------------------
// | 消息队列
// +----------------------------------------------------------------------

namespace helper;

use Elastic\Elasticsearch\Client;
use Elastic\Elasticsearch\ClientBuilder;
use Elastic\Elasticsearch\Exception\AuthenticationException;
use Elastic\Elasticsearch\Exception\ClientResponseException;
use Elastic\Elasticsearch\Exception\MissingParameterException;
use Elastic\Elasticsearch\Exception\ServerResponseException;
use Exception;

class Elasticsearch
{
    private array $config;

    private Client $client;

    /**
     * 索引 index对应关系型数据（以下简称MySQL）里面的数据库，而不是对应MySQL里面的索引
     * @var string
     */
    public string $index;

    /**
     * 初始化
     * @param array $config
     * @param string $index
     * @throws AuthenticationException
     */
    public function __construct(array $config, string $index)
    {
        //加载配置文件
        //$this->config = config('elasticsearch');
        //构建客户端对象 ->setBasicAuthentication($this->config['username'], $this->config['password'])
        $this->client = ClientBuilder::create()->setHosts(['localhost:9200'])->build();
        $this->index = $index;
    }

    public function demo()
    {
        // 1.创建索引
        // type: boolean composite date double geo_point ip keyword long lookup
        $arr = [
            'id' => [
                'type' => 'integer'
            ],
            'title' => [
                'type' => 'keyword'// 相当于数据查询是的 = 张三你好，必须找到张三你好
            ],
            'content' => [
                'type' => 'text',
                'analyzer' => 'ik_max_word',// 中文分词 ik_max_word: 张三你好   张三  你好 张三你好;ik_smart
                'search_analyzer' => 'ik_max_word'
            ],
        ];
        $this->createIndex($arr);

        // 2.创建文档模板
        //$this->putMapping($arr);

        // 3.添加文档
        // 3.1)添加单条
        $this->createDoc(['id' => 0, 'title' => '杜甫', 'content' => '春夜喜雨'], 0);

        // 3.2）添加多条
        $data = [
            ['id' => 1, 'title' => '小赵', 'content' => '好雨知时节'],
            ['id' => 2, 'title' => '小钱', 'content' => '当春乃发生'],
            ['id' => 3, 'title' => '小孙', 'content' => '随风潜入夜'],
            ['id' => 4, 'title' => '小李', 'content' => '润物细无声'],
            ['id' => 5, 'title' => '小周', 'content' => '野径云俱黑'],
            ['id' => 6, 'title' => '小吴', 'content' => '江船火独明'],
            ['id' => 7, 'title' => '小郑', 'content' => '晓看红湿处'],
            ['id' => 8, 'title' => '小王', 'content' => '花重锦官城'],
        ];

        /**
         * 第一行指定请求与索引和类型,可以选择的请求有"create","index","delete","update"; "_index"指定索引名,"_id"指定id
         * 第二行指定插入的内容.
            { "index" : { "_index" : "test", "_id" : "1" } }
            { "field1" : "value1" }
            { "delete" : { "_index" : "test", "_id" : "2" } }
            { "create" : { "_index" : "test", "_id" : "3" } }
            { "field1" : "value3" }
            { "update" : {"_id" : "1", "_index" : "test"} }
            { "doc" : {"field2" : "value2"} }
         */

        $params = [];
        foreach ($data as $v) {
            $params['body'][] = [
                'index' => [
                    "_index" => $this->index,
                    "_id"    => $v['id']
                ]
            ];
            $params['body'][] = $v;
        }

        $result = $this->bulkDoc($params);
        print_r($result);

        // 4.搜索文档
        $body = [
            "query" => [
                // 搜索全部
                "match_all" => [

                ]
            ],
            "sort" => [
                ["id" => ["order" => "asc"]]
            ],
            "from" => 10,// 开始偏移量。
            "size" => 10,// 返回数
        ];

        $body = [
            'query' => [
                // 搜索特定
                'match' => [
                    'content' => "好雨"
                ]
            ],
        ];

        $body = [
            'query' => [
                // 搜索特定
                'match' => [
                    'content' => [
                        'query' => '好雨',
                        'boost' => 3, // 权重
                        //'minimum_should_match'  => '90%'  //相似度，匹配度
                    ]
                ]
            ],
        ];

        $body = [
            'query' => [
                'constant_score' => [ //非评分模式执行
                    'filter' => [ //过滤器，不会计算相关度，速度快
                        'term' => [ //精确查找，不支持多个条件
                            'title' => '端午'
                        ]
                    ],
                    "boost" => 1.2
                ]
            ]
        ];

        $result = $this->searchDoc($body);
        print_r($result);

        // 5.获取文档
        $result = $this->getDoc(1);
        print_r($result);
    }

    /**
     * 获取信息
     */
    public function info()
    {
        $response = $this->client->info();
        echo $response->getStatusCode(); // 200
        echo (string) $response->getBody(); // Response body in JSON

        var_dump($response->asArray());  // response body content as array

        echo $response->version->number; // 8.0.0
        var_dump($response->asObject()); // response body content as object
        var_dump($response->asString()); // response body as string (JSON)
        var_dump($response->asBool());   // true if HTTP response code between 200 and 300
    }

    /**
     * 创建文档 文档相当于数据库中的表
     * @param array $body
     * @param string $id
     * @return array
     */
    public function createDoc(array $body, string $id = ''): array
    {
        $params = [
            'index' => $this->index,//索引
            'id'    => $id,//唯一标识,会自动生成
            'body' => $body
        ];

        try {
            $response = $this->client->index($params);
        } catch (ClientResponseException $e) {
            // manage the 4xx error
        } catch (ServerResponseException $e) {
            // manage the 5xx error
        } catch (Exception $e) {
            // eg. network error like NoNodeAvailableException
        }
        return $response->asArray();// response body content as array
    }

    /**
     * 获取一个文档(根据唯一id查询数据)
     * @param string $id
     * @return array
     */
    public function getDoc(string $id): array
    {
        $params = [
            'index' => $this->index,
            'id' => $id
        ];
        try {
            $response = $this->client->get($params);
        } catch (ClientResponseException|MissingParameterException|ServerResponseException $e) {
            print_r($e->getMessage());
        }
        return $response->asArray();
    }

    /**
     * 删除一个文档（仅删除一条记录）
     * @param int $id
     * @return boolean
     */
    public function deleteDoc(int $id): bool
    {
        try {
            $response = $this->client->delete([
                'index' => $this->index,
                'id' => $id
            ]);
        } catch (ClientResponseException $e) {
            if ($e->getCode() === 404) {
                // the document does not exist
                return false;
            }
        }
        if ($response['acknowledge'] === 1) {
            // the document has been delete
            return true;
        }
        return false;
    }

    /**
     * 索引一个文档（创建一条数据）
     * @param $id
     * @param $arr
     * @return array
     * @throws \Exception
     */
    public function updateDoc($id, $arr)
    {
        try {
            $params = [
                'index' => $this->index,
                'id' => $id,
                'body' => $arr
            ];
            $response = $this->client->update($params);
        } catch (\Exception $e) {
            throw $e;
        }
        return $response->asArray();
    }

    public function multiGetDoc()
    {

    }

    /**
     * 批量操作文档
     * @param array $params
     * @return array
     */
    public function bulkDoc(array $params): array
    {
        try {
            $response = $this->client->bulk($params);
            return $response->asArray();
        } catch (ClientResponseException|MissingParameterException|ServerResponseException $e) {
            return ['code' => $e->getCode(), 'message' => $e->getMessage()];
        }
    }

    public function DeleteByQueryDoc()
    {

    }

    public function UpdateByQueryDoc()
    {

    }

    public function ReindexDoc()
    {

    }

    /**
     * 查询文档是否存在
     * @return array|bool
     * @throws \Exception
     */
    public function existsDoc($id)
    {
        try {
            $params = [
                'index' => $this->index,
                'id' => $id
            ];
            $response = $this->client->exists($params);
        } catch (\Exception $e) {
            throw $e;
        }
        return $response;
    }

    /**
     * 搜索一个文档（全文搜索）
     * @param array $body
     */
    public function searchDoc(array $body): array
    {
        $params = [
            'index' => $this->index,
            'body' => $body
        ];
        try {
            $response = $this->client->search($params);
        } catch (ClientResponseException|MissingParameterException|ServerResponseException $e) {
            print_r($e->getMessage());
        }
        //printf("Total docs: %d\n", $response['hits']['total']['value']);
        //printf("Max score : %.4f\n", $response['hits']['max_score']);
        //printf("Took      : %d ms\n", $response['took']);
        //print_r($response['hits']['hits']); // documents
        return $response->asArray();
    }

    /**
     * 创建索引
     * @param array $arr
     */
    public function createIndex(array $arr = [])
    {
        $params = [
            'index' => $this->index,// 生成索引的名称
            'body' => [ // 类型 body
                'settings' => [//自定义设置配置
                    // 数据分片数
                    'number_of_shards' => 5,
                    // 数据备份数
                    'number_of_replicas' => 1
                ],
                'mappings' => [
                    '_source' => [
                        'enabled' => true
                    ],
                    // 字段 类似表字段，设置类型
                    'properties' => $arr
                ]
            ],
            'include_type_name' => true//大意就是 ES 7. 中要要么移除 doc type 或者在使用的时候需要加上？includetypename=true,
        ];
        try {
            return $this->client->indices()->create($params);
        } catch (ClientResponseException|MissingParameterException|ServerResponseException $e) {
            print_r($e->getMessage());
        }
    }

    /**
     * 删除一个索引（删除整个数据库）
     * @return array
     */
    public function deleteIndex(): array
    {
        $deleteParams = [
            'index' => $this->index,
        ];
        try {
            $response = $this->client->indices()->delete($deleteParams);
        } catch (ClientResponseException|MissingParameterException|ServerResponseException $e) {

        }
        return $response->asArray();
    }

    /**
     * 查询索引是否存在
     * @return array|bool
     * @throws \Exception
     */
    public function existsIndex()
    {
        try {
            $params = [
                'index' => $this->index,
            ];
            $response = $this->client->indices()->exists($params);
        } catch (\Exception $e) {
            throw $e;
        }
        return $response;
    }

    /**
     * 更新索引的映射 mapping
     * @param array $arr
     * @return array ['id'=>['type'=>'','index'=>'analyzed','analyzer'=>'']] analyzed/not_analyzed
     * @throws \Exception
     */
    public function putMapping($arr)
    {
        try {
            $params = [
                'index' => $this->index,
                'body' => $arr
            ];
            $response = $this->client->indices()->putMapping($params);
        } catch (\Exception $e) {
            throw $e;
        }
        return $response;
    }

    /**
     * 获取索引映射 mapping
     * @return array
     * @throws \Exception
     */
    public function getMapping()
    {
        $params = [
            'index' => $this->index,
        ];
        return $this->client->indices()->getMapping($params);
    }

    /**
     * 批量插入数据
     * @param array $arr
     * @return array
     * @throws \Exception
     */
    public function bulk($arr)
    {
        try {
            $params = [
                'index' => $this->index,
                'body' => $arr
            ];
            $response = $this->client->bulk($params);
        } catch (\Exception $e) {
            throw $e;
        }
        return $response;
    }

    /**
     * 批量查询，只能根据id来查
     * @param array $arr
     * @return array
     * @throws \Exception
     */
    public function mGet($arr)
    {
        try {
            $params = [
                'index' => $this->index,
                'body' => [
                    'query' => [ // 查询时必须制定的`query`参数
                        'ids' => $arr
                    ]
                ]
            ];
            $response = $this->client->mget($params);
        } catch (\Exception $e) {
            throw $e;
        }
        return $response;
    }
}